/*
 * Copyright (c) 2007 Compl Yue Still, All Rights Reserved.
 */

package hbi.wire.java.host;

import hbi.domain.java.JavaDomain;
import hbi.wire.ProtocolError;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

class BinaryChunks implements DataTag
{

    public Object getTagValue()
    {
        return read;
    }

    protected boolean isFinalChunkTag(int tag)
    {
        return tag == 'B';
    }

    protected boolean isNonFinalChunkTag(int tag)
    {
        return tag == 'b';
    }

    public void reset()
    {
        isFinalChunk = isFinalChunkInitial;
        expectingNextChunkTag = false;
        read = null;
        readPos = 0;
    }

    private final boolean isFinalChunkInitial;

    private boolean isFinalChunk;

    protected BinaryChunks(boolean isFinalChunk)
    {
        this.isFinalChunkInitial = this.isFinalChunk = isFinalChunk;
    }

    private boolean expectingNextChunkTag = false;

    private byte[] read = null;

    private int readPos = 0;

    public final boolean processIncomingData(ByteBuffer buffer)
        throws IOException
    {
        if (read == null)
        // initial length not read yet
        {
            if (buffer.remaining() < 2)
            // length not available yet
                return false;

            int len = buffer.getShort() & 0xFFFF;
            if (len <= 0)
            {
                if (isFinalChunk)
                {
                    read = new byte[0];
                    return true;
                }
                expectingNextChunkTag = true;
            }
            if (len > JavaDomain.MAX_BINARY_LENGTH)
                throw new SecurityException("Banged with extreme long blob of "
                    + len + " bytes.");
            read = new byte[len];
        }

        while (buffer.hasRemaining())
        {
            if (expectingNextChunkTag)
            {
                assert readPos == read.length : "should at chunk boundary here";

                int b = buffer.get() & 0xFF;
                if (this.isFinalChunkTag(b))
                    isFinalChunk = true;
                else if (!this.isNonFinalChunkTag(b))
                    throw new ProtocolError("Unexpected tag: " + b);
                expectingNextChunkTag = false;

                if (buffer.remaining() < 2) return false;
            }

            if (readPos >= read.length)
            // length of next chunk not read yet
            {
                assert !isFinalChunk : "or we should have finished";

                if (buffer.remaining() < 2)
                // length not available yet
                    return false;

                int len = buffer.getShort() & 0xFFFF;
                if (len <= 0)
                {
                    if (isFinalChunk)
                    {
                        return true;
                    }
                    expectingNextChunkTag = true;
                    continue;
                }
                len += read.length;
                if (len > JavaDomain.MAX_BINARY_LENGTH)
                    throw new SecurityException(
                        "Banged with extreme long blob of " + len + " bytes.");
                read = Arrays.copyOf(read, len);

                if (!buffer.hasRemaining()) return false;
            }

            int len2Read = Math.min(read.length - readPos, buffer.remaining());
            buffer.get(read, readPos, len2Read);
            readPos += len2Read;

            if (readPos >= read.length)
            {
                if (isFinalChunk)
                {
                    return true;
                }
                expectingNextChunkTag = true;
            }
        }

        return false;
    }

}
